#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>      /* For platform devices */
#include <linux/gpio/consumer.h>        /* For GPIO Descriptor interface */
#include <linux/interrupt.h>            /* For IRQ */
#include <linux/of.h>                   /* For DT*/

/*
 * Let us consider the bellow mapping
 *
 */
static struct gpio_desc *red, *green, *blue, * my_key;
static int irq;
static int lighting_led_index = 0;
/**
 * 中断上半部处理函数
 * 不能被其他中断所中断
 * 所以处理速度一定要快
 */
static irqreturn_t my_inrrupt_handler_top(int irq, void *dev_id)
{
 
    lighting_led_index = (1+lighting_led_index) % 3;
    
   
/*
    for (i=0; i < sizeof(leds); i++)
    {
        if(i==lighting_led_index){
            struct gpio_desc * l = leds[i];
            gpiod_set_value(l, 1);
        }else{
            gpiod_set_value(leds[i], 0);
        }
    }
*/
    /*
        返回 IRQ_WAKE_THREAD 代表把中断下半部中继续执行中断任务
     */
    return IRQ_WAKE_THREAD; 
}
/**
 * 中断函数下半部，会被其他中断所中断
 */
irqreturn_t my_inrrupt_handler_bottom(int req, void * data){

    pr_info("enter my_interrupt function, irq is %d\n",irq);

    switch(lighting_led_index){
        case 0:{
            gpiod_set_value(red, 1);
            gpiod_set_value(green, 0);
            gpiod_set_value(blue, 0);
            break;
        }
        case 1:{
             gpiod_set_value(red, 0);
            gpiod_set_value(green, 1);
            gpiod_set_value(blue, 0);
            break;
        }
        case 2:{
             gpiod_set_value(red, 0);
            gpiod_set_value(green, 0);
            gpiod_set_value(blue, 1);
            break;
        }
      
    }

    pr_info("btn1 interrupt: Interrupt! lighting gpio is %d)\n", lighting_led_index);

    return IRQ_HANDLED;
}

static const struct of_device_id gpiod_dt_ids[] = {
    { .compatible = "luo,gpio-descriptor-sample", },
    { .compatible = "yan,gpio-descriptor-sample", },
    { /* sentinel */ }
};


static int my_pdrv_probe (struct platform_device *pdev)
{
    int retval;
    struct device *dev = &pdev->dev;

    /*
     * We use gpiod_get/gpiod_get_index() along with the flags
     * in order to configure the GPIO direction and an initial
     * value in a single function call.
     *
     * One could have used:
     *  red = gpiod_get_index(dev, "led", 0);
     *  gpiod_direction_output(red, 0);
     */
    red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW);
    green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW);
    blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_LOW);

    // 以此设置 0 1 状态，可以控制主板上led为 红 绿 蓝
    gpiod_set_value(red,0);
    gpiod_set_value(green,0);
    gpiod_set_value(blue,1);
    /*
     * Configure Button GPIOs as input
     *
     * After this, one can call gpiod_set_debounce()
     * only if the controller has the feature
     * For example, to debounce  a button with a delay of 200ms
     *  gpiod_set_debounce(btn1, 200);
     */
    my_key = gpiod_get(dev, "my-key", GPIOD_IN);

    // 去除抖动
    gpiod_set_debounce(my_key, 200);

    irq = gpiod_to_irq(my_key);
    /**
     * 参数handler对应的函数执行于中断上下文，thread_fn参数对应的函数则执行于内核线程。
     * 如果handler结束的时候，返回值是IRQ_WAKE_THREAD，
     * 内核会调度对应线程执行thread_fn对应的函数。
     * 
     * request_threaded_irq（​）和devm_request_threaded_irq（​）支持在irqflags中设置IRQF_ONESHOT标记，
     * 这样内核会自动帮助我们在中断上下文中屏蔽对应的中断号，
     * 而在内核调度thread_fn执行后，重新使能该中断号。
     * 对于我们无法在上半部清除中断的情况，IRQF_ONESHOT特别有用，
     * 避免了中断服务程序一退出，中断就洪泛的情况。
     */
    retval = devm_request_threaded_irq(dev,irq,
                            my_inrrupt_handler_top,
                            my_inrrupt_handler_bottom,
                            IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                            "my-led-gpio-interrupt", NULL);
    pr_info("Hello! device probed!\n");
    return 0;
}

static int my_pdrv_remove(struct platform_device *pdev)
{
    // 使用了 devm_ 来请求中断，就不需要手动释放中断了
    //free_irq(irq, NULL);
    gpiod_put(red);
    gpiod_put(green);
    gpiod_put(blue);
    gpiod_put(my_key);

    pr_info("good bye reader!\n");
    return 0;
}

static struct platform_driver mypdrv = {
    .probe      = my_pdrv_probe,
    .remove     = my_pdrv_remove,
    .driver     = {
        // name 一定不能为 null
        // 否则此内核模块直接加载不了
        // 加载时会报 Segment Fail
        .name     = "using_gpio_subsystem",
        .of_match_table = of_match_ptr(gpiod_dt_ids),  
        .owner    = THIS_MODULE,
    },
};
module_platform_driver(mypdrv);

MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_LICENSE("GPL");
